/*
 * matrix.cpp
 *
 *  Created on: 02/06/2011
 *      Author: Idan
 */
#include "matrix.h"
#include <cassert>
using namespace MatrixExceptions;
 //-- help functions --//

bool Matrix::isRowInIndex(int row) const{
	return row>0 && row<=rows();
}
bool Matrix::isColumnInIndex(int column) const{
	return column>0 && column<=columns();
}
bool Matrix::isInIndex(int row, int column) const{
	return isColumnInIndex(column) && isRowInIndex(row);
}

bool Matrix::isRowOrColumnNegative(int row, int column){
	return row<0 || column<0;
}

bool Matrix::isMatrixSameMultiplayDimensions(const Matrix& matrix) const{
	return columns()==matrix.rows();
}
bool Matrix::isMatrixSameDimensions(const Matrix& matrix) const{
	return columns()==matrix.columns() && rows()==matrix.rows();
}
bool Matrix::isRowsEqualColumns() const{
	return columns()==rows();
}
void Matrix::copyMatrixData(const Matrix& matrix){
	matrixDouble = new double*[matrix.rows()];
	assert(matrix.columns()>0 && matrix.rows()>0);
	for (int i = 1; i <= matrix.rows(); ++i) {
		matrixDouble[i-1] = new double[matrix.columns()];
		rowsNumber++;
		for (int j = 1; j <= matrix.columns(); ++j) {
			(*this)(i,j) = matrix(i,j);
		}
	}
}


void Matrix::deleteDoubleData(){
	for (int i = 0; i < rows(); ++i) {
		delete[] matrixDouble[i];
	}
	delete[] matrixDouble;
}
//--------------------//
Matrix::Matrix(int rowSize, int colSize):
	rowsNumber(0),
	columnsNumber(colSize),
	matrixDouble(new double*[rowSize]){
		if(Matrix::isRowOrColumnNegative(rowSize,colSize)){
			throw BadDimensions();
		}
		for (int i = 1; i <= rowSize; ++i) {
			matrixDouble[i-1] = new double[colSize];
			rowsNumber++;
			for (int j = 1; j <= columns(); ++j) {
				(*this)(i,j) = 0;
			}
		}
}

Matrix::Matrix(const Matrix& matrix):
	rowsNumber(0),
	columnsNumber(matrix.columns()){
			copyMatrixData(matrix);
}

Matrix Matrix::createUnit(const int matrixSize){
	if(matrixSize<0){
		throw BadDimensions();
	}
	Matrix returnMatrix = Matrix(matrixSize,matrixSize);
	for (int i = 1; i <= returnMatrix.rows(); ++i) {
			returnMatrix(i,i) = 1;
	}
	return returnMatrix;
}

Matrix& Matrix::operator =(const Matrix& matrix){
	if(this==&matrix){
		return *this;
	}
	deleteDoubleData();
	rowsNumber=0;
	columnsNumber = matrix.columns();
	copyMatrixData(matrix);
	return *this;

}

int Matrix::rows() const{
	return rowsNumber;
}

int Matrix::columns() const{
	return columnsNumber;
}

double& Matrix::operator()(int row, int column){
	if(!isInIndex(row,column)){
		throw IndexOutOfRange();
	}
	return matrixDouble[row-1][column-1];
}

const double& Matrix::operator()(int row, int column) const{
	if(!isInIndex(row,column)){
			throw IndexOutOfRange();
		}
	return matrixDouble[row-1][column-1];
}
Matrix& Matrix::operator*=(const Matrix& matrix){
	if(!isMatrixSameMultiplayDimensions(matrix)){
		throw BadDimensions();
	}
	Matrix result = Matrix(rows(),matrix.columns());
	for (int i = 1; i <= rows(); ++i) {
		for (int j = 1; j <= matrix.columns(); ++j) {
			for (int k = 1; k <= columns(); ++k) {
				result(i,j) += (*this)(i,k)*matrix(k,j);
			}
		}
	}
	(*this)=result;
	return *this;
}
Matrix operator*(const Matrix& matrixA,const Matrix& matrixB){
	Matrix temp = matrixA;
	return temp*=matrixB;
}



Matrix& Matrix::operator*=(double scalar){
	for (int i = 1; i <= rows(); ++i) {
		for (int j = 1; j <= columns(); ++j) {
			(*this)(i,j)*=scalar;
		}
	}//how to do that?
	return *this;
}

Matrix Matrix::operator*(double scalar) const{
	Matrix temp = Matrix(*this);
	return temp*=scalar;
}


//Matrix& Matrix::operator*=(const double scalar) const{
//	return *this*scalar;
//}


bool Matrix::operator ==(const Matrix& matrix) const{
	if(!isMatrixSameDimensions(matrix)){
		throw BadDimensions();
	}
	for (int i = 1; i <= rows(); ++i) {
		for (int j = 1; j <= columns(); ++j) {
			if(!(IS_DOUBLE_EQUAL((*this)(i,j),matrix(i,j)))){
				return false;
			}
		}
	}
	return true;
}
bool Matrix::operator !=(const Matrix& matrix) const{
	return !((*this)==matrix);
}

Matrix& Matrix::operator +=(const Matrix& matrix){
	if(!isMatrixSameDimensions(matrix)){
		throw BadDimensions();
	}
	for (int i = 1; i <= rowsNumber; ++i) {
		for (int j = 1; j <= columnsNumber; ++j) {
			(*this)(i,j) += matrix(i,j);
		}
	}
	return *this;
}

Matrix operator +(const Matrix& matrixA,const Matrix& matrixB){
	Matrix matrixC = matrixA;
	return matrixC+=matrixB;
}
Matrix& Matrix::operator-=(const Matrix& matrix){
	return (*this)+=(-matrix);
}
Matrix Matrix::operator-() const{
	Matrix temp(*this);
	return (temp)*(-1);
}

Matrix operator -(const Matrix& matrixA,const Matrix& matrixB){
	Matrix temp(matrixA);
	return temp-=matrixB;
}

void Matrix::transposeCells(int i, int j){
	assert(isInIndex(i,j));
	double temp = (*this)(i,j);
	(*this)(i,j)=(*this)(j,i);
	(*this)(j,i)=temp;
}

Matrix Matrix::transpose() const{
	if(!isRowsEqualColumns()){
		throw BadDimensions();
	}
	Matrix toReturn = Matrix(*this);
	for (int i = 1; i <= columns(); ++i) {
		for (int j = i; j <= rows(); ++j) {
			toReturn.transposeCells(i,j);
		}
	}
	return toReturn;
}


void Matrix::swapRows(int row1, int row2){
	assert(isRowInIndex(row1)&&isRowInIndex(row2));
	row1--;
	row2--;
	double* temp = matrixDouble[row1];
	matrixDouble[row1]=matrixDouble[row2];
	matrixDouble[row2]=temp;
}

void Matrix::multiplayRowByScalar(int row,double scalar){
	assert(isRowInIndex(row));
	for (int i = 1; i <= columns(); ++i) {
		(*this)(row,i)*=scalar;
	}
}

void Matrix::substractRow(int row1,int row2,double scalar){
	assert(isRowInIndex(row1) && isRowInIndex(row2));
	for (int i = 1; i <= columns(); ++i) {
		(*this)(row1,i)-=scalar*(*this)(row2,i);
	}
}

bool Matrix::isMatrixSqure() const{
	return rows() == columns();
}

void Matrix::arrangeStartPointGivenColumn(int columnIndex,
		Matrix& matrixToInverse){
	assert(isColumnInIndex(columnIndex));
	int startPoint = columnIndex;
	while( IS_DOUBLE_EQUAL((*this)(startPoint,columnIndex),0) ){
		if (startPoint==rows()){
			throw Singular();
		}
		startPoint++;
	}
	swapRows(columnIndex,startPoint);
	matrixToInverse.swapRows(columnIndex,startPoint);
	assert((*this)(columnIndex,columnIndex)!=0);
	double scalarToDivideBy = (*this)(columnIndex,columnIndex);
	multiplayRowByScalar(columnIndex,1/scalarToDivideBy);
	matrixToInverse.multiplayRowByScalar(columnIndex,
			1/scalarToDivideBy);
}
double substractNumber = 0;
void Matrix::toUnit(Matrix& matrixToInverse){
	for (int i = 1; i <= columns(); ++i) {
		arrangeStartPointGivenColumn(i,matrixToInverse);
		for (int j = 1; j <= rows(); ++j) {
			if(j==i){
				continue;
			}
			substractNumber = (*this)(j,i);
			substractRow(j,i,substractNumber);
			matrixToInverse.substractRow(j,i,substractNumber);
		}
	}
}

Matrix Matrix::inverse() const{
	if(!isMatrixSqure()){
		throw BadDimensions();
	}
	Matrix toWorkOn = *this;
	Matrix matrixToInverse = createUnit(rows());
	toWorkOn.toUnit(matrixToInverse);
	return matrixToInverse;
}

Matrix::~Matrix(){
	deleteDoubleData();
}
